Namespace ShapeLibrary.Design
    ''' 
    ''' ShapeContainerRootDesigner is the user interface we present to users when they
    ''' create a new shape container.  Individual shapes can be visually designed
    ''' on this container.  We also add a toolbox item filter to the class definition
    ''' that requires any toolbox items to have the filter name of
    ''' "ShapeLibrary.Shape". We also define this filter name
    ''' in the Shape base class.  This disables all toolbox items that are not derived
    ''' from Shape.
    ''' 
    <ToolboxItemFilter("ShapeLibrary.Shape", ToolboxItemFilterType.Require)> _
    Friend Class ShapeContainerRootDesigner
        Inherits ComponentDesigner
        Implements IRootDesigner, IToolboxUser

        Private m_view As DesignerView
        Private m_currentSelection As ICollection
        Private m_menuCommandService As IMenuCommandService

        ' The menu commands we handle
        '
        Private m_menuCommands As MenuCommand()

        ''' 
        ''' This is a helper property that returns the ShapeContainer object we're designing.
        ''' 
        Protected Overridable ReadOnly Property ShapeContainer() As ShapeContainer
            Get
                Return CType(Component, ShapeContainer)
            End Get
        End Property

        ''' 
        ''' We override Dispose here.  This designer listens to some events from outside services
        ''' and we must disconnect these event listeners when we're disposed.  Otherwise, we might
        ''' never GC, and worse, our designer may receive events after it has been destroyed.
        ''' 
        Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

            If disposing Then
                If Not (m_view Is Nothing) Then
                    m_view.Dispose()
                End If

                Dim cs As IComponentChangeService = CType(GetService(GetType(IComponentChangeService)), IComponentChangeService)
                If Not (cs Is Nothing) Then
                    RemoveHandler cs.ComponentChanged, New ComponentChangedEventHandler(AddressOf OnComponentChanged)
                    RemoveHandler cs.ComponentChanging, New ComponentChangingEventHandler(AddressOf OnComponentChanging)
                End If

                Dim ss As ISelectionService = CType(GetService(GetType(ISelectionService)), ISelectionService)
                If Not (ss Is Nothing) Then
                    RemoveHandler ss.SelectionChanged, New EventHandler(AddressOf OnSelectionChanged)
                End If

                If Not (m_menuCommands Is Nothing) And Not (m_menuCommandService Is Nothing) Then
                    Dim mc As MenuCommand
                    For Each mc In m_menuCommands
                        m_menuCommandService.RemoveCommand(mc)
                    Next mc
                    m_menuCommands = Nothing
                End If
            End If

            MyBase.Dispose(disposing)
        End Sub

        '''
        '''   We need to do this because Vb does not allow nested classes to have
        '''   access to protected methods.
        '''
        Friend Function GetServiceVB(ByVal serviceType As Type) As Object
            Return GetService(serviceType)
        End Function

        ''' 
        ''' We override Initialize here so we can connect the events we need to listen to.
        ''' 
        Public Overrides Sub Initialize(ByVal component As IComponent)
            MyBase.Initialize(component)

            ' We need to listen to change events.  If a shape changes,
            ' we need to invalidate our view.
            '
            Dim cs As IComponentChangeService = CType(GetService(GetType(IComponentChangeService)), IComponentChangeService)
            If Not (cs Is Nothing) Then
                AddHandler cs.ComponentChanged, New ComponentChangedEventHandler(AddressOf OnComponentChanged)
                AddHandler cs.ComponentChanging, New ComponentChangingEventHandler(AddressOf OnComponentChanging)
            End If

            ' Add the menu commands we support.  We must be a member of the VSIP program to
            ' define new menu items, but we can handle any item located within the StandardCommands
            ' class because Visual Studio already defines them.
            '
            m_menuCommandService = CType(GetService(GetType(IMenuCommandService)), IMenuCommandService)
            If Not (m_menuCommandService Is Nothing) Then
                m_menuCommands = New MenuCommand() _
         { _
          New MenuCommand(New EventHandler(AddressOf OnMenuCut), StandardCommands.Cut), _
          New MenuCommand(New EventHandler(AddressOf OnMenuCopy), StandardCommands.Copy), _
          New ImmediateMenuCommand(New EventHandler(AddressOf OnMenuPasteStatus), New EventHandler(AddressOf OnMenuPaste), StandardCommands.Paste), _
          New MenuCommand(New EventHandler(AddressOf OnMenuDelete), StandardCommands.Delete) _
         }

                Dim mc As MenuCommand
                For Each mc In m_menuCommands
                    m_menuCommandService.AddCommand(mc)
                Next mc
            End If

            ' Select our base shape.  By default there is nothing selected but that looks
            ' strange (the property grid is empty).
            '
            Dim ss As ISelectionService = CType(GetService(GetType(ISelectionService)), ISelectionService)
            If Not (ss Is Nothing) Then
                ss.SetSelectedComponents(New Object() {component}, SelectionTypes.Replace)
                AddHandler ss.SelectionChanged, New EventHandler(AddressOf OnSelectionChanged)
            End If

            ' We want the user to be able to place shapes on other designers, like windows forms.
            ' The user could always use the customize toolbox dialog to browse to the library this
            ' shape is stored in, but that's not a very RAD way of doing things.  It's much more
            ' convenient to place a toolbox item on the toolbox for this shape.  We do this with
            ' a toolbox item called a "Linked Tool".  Linked tools are linked to a particular designer
            ' host and are automatically removed when the solution containing this project is closed.
            ' To create a linked tool we create a toolbox item that contains the fully qualified 
            ' class name of the shape we're designing.  Unfortunatley, that name isn't available yet.
            ' To work around this, we must wait until the designer is fully loaded, and then add
            ' the toolbox item.  We can do this easily through the IDesignerHost interface:
            '
            Dim host As IDesignerHost = CType(GetService(GetType(IDesignerHost)), IDesignerHost)
            If Not (host Is Nothing) Then
                AddHandler host.LoadComplete, New EventHandler(AddressOf OnLoadComplete)
            End If
        End Sub

        ''' 
        ''' This is a helper method that invalidates our control using the bounding rectangle of
        ''' the given shape.  The parameter is typed as object for convenience; this method
        ''' actually looks for a BoundingBox property on the object that has a property type of
        ''' Rectangle, so anything that satisifies this will work.
        ''' 
        Private Sub InvalidateShapeBounds(ByVal shape As Object)
            Dim prop As PropertyDescriptor = TypeDescriptor.GetProperties(shape)("BoundingBox")
            If Not (m_view Is Nothing) AndAlso (Not (prop Is Nothing)) AndAlso prop.PropertyType Is GetType(Rectangle) Then
                Dim bounds As Rectangle = CType(prop.GetValue(shape), Rectangle)
                bounds.Inflate(ShapeDesigner.AdornmentDimensions)
                m_view.Invalidate(bounds)
            End If
        End Sub

        ''' 
        ''' Event handler for the ComponentChanged event.  Here we invalidate the control
        ''' so it repaints at the region where the given component is located.
        ''' 
        Private Sub OnComponentChanged(ByVal sender As Object, ByVal ce As ComponentChangedEventArgs)
            If Not (m_view Is Nothing) AndAlso TypeOf ce.Component Is Shape Then
                InvalidateShapeBounds(ce.Component)
            End If
        End Sub

        ''' 
        ''' Event handler for the ComponentChanging event.  Here we invalidate the control
        ''' so it repaints at the region where the given component is located.  When
        ''' a shape is moved or sized, there will be a ComponentChanging event before
        ''' the shape moves, and then a ComponentChanged event. We need to invalidate
        ''' the control at both positions.
        ''' 
        Private Sub OnComponentChanging(ByVal sender As Object, ByVal ce As ComponentChangingEventArgs)
            If Not (m_view Is Nothing) AndAlso TypeOf ce.Component Is Shape Then
                InvalidateShapeBounds(ce.Component)
            End If
        End Sub

        ''' 
        ''' Event handler for the load complete event.  We listen to this event so that
        ''' after the shape designer is loaded we can add a toolbox item to the toolbox.
        ''' We must wait until after the load completes because the fully qualified name
        ''' of the base class is not known until that time.
        ''' 
        Private Sub OnLoadComplete(ByVal sender As Object, ByVal e As EventArgs)
            Dim host As IDesignerHost = CType(sender, IDesignerHost)
            AddHandler host.LoadComplete, New EventHandler(AddressOf OnLoadComplete)

            ' Now get a hold of the toolbox service and add an icon for our user control.  The
            ' toolbox service will automatically maintain this icon as long as our file lives.
            '
            Dim tbx As IToolboxService = CType(GetService(GetType(IToolboxService)), IToolboxService)

            If Not (tbx Is Nothing) Then
                Dim fullClassName As String = host.RootComponentClassName
                Dim item As ToolboxItem = New ToolboxItem()
                item.TypeName = fullClassName

                Dim idx As Integer = fullClassName.LastIndexOf(".")
                If idx <> -1 Then
                    item.DisplayName = fullClassName.Substring(idx + 1)
                Else
                    item.DisplayName = fullClassName
                End If

                item.Bitmap = New Bitmap(GetType(ShapeDesigner), "CustomShapeContainer.bmp")
                item.Bitmap.MakeTransparent()

                ' Toolbox items are mutable until Lock is called on them.  This
                ' allows them to be very flexible without requiring a bunch of
                ' different constructors.  Once lock is called, they are "fixed"
                ' and cannot be changed.  After creating a toolbox item you should
                ' always call Lock before handing it to the toolbox service.  Otherwise
                ' other people could change it on you.
                item.Lock()

                ' A "linked" toolbox item is one that is linked to our designer.  It
                ' follows our designer, rather than living forever on the toolbox.
                ' The tool is automatically removed when the project is closed, and
                ' re-added when the project is opened again.
                tbx.AddLinkedToolboxItem(item, "Shapes", host)
            End If
        End Sub

        ''' 
        ''' Command handler for the Copy command.  This is called when the user copies the
        ''' currentl selection to the clipboard.
        ''' 
        Private Sub OnMenuCopy(ByVal sender As Object, ByVal e As EventArgs)
            ' What is this?  Components typically do not support serialization, but they all 
            ' support being saved in a designer.  How this save is accomplished is dependent
            ' on what is hosting the designer.  Whatever environment that is hosting us
            ' provides a service called IDesignerSerializationService.  This service can
            ' take an arbitrary collection of objects and convert them to an object
            ' that can be serialized.  The object that comes out of a Serialize call
            ' on this service can then be passed to the clipboard, or through a
            ' drag and drop operation.
            '
            Dim ds As IDesignerSerializationService = CType(GetService(GetType(IDesignerSerializationService)), IDesignerSerializationService)
            If Not (ds Is Nothing) Then
                Dim serializedData As Object = ds.Serialize(m_currentSelection)
                Dim data As DataObject = New DataObject("ShapeLibraryData", serializedData)
                Clipboard.SetDataObject(data, True)
            End If
        End Sub

        ''' 
        ''' Command handler for the Cut command.  This is just a combination of 
        ''' Copy and Delete that we wrap in a single transaction.
        ''' 
        Private Sub OnMenuCut(ByVal sender As Object, ByVal e As EventArgs)
            Dim host As IDesignerHost = CType(GetService(GetType(IDesignerHost)), IDesignerHost)
            If Not (host Is Nothing) AndAlso Not (m_currentSelection Is Nothing) AndAlso m_currentSelection.Count > 0 Then
                Dim trans As DesignerTransaction = host.CreateTransaction("Cut " + m_currentSelection.Count + " component(s)")
                Try
                    OnMenuCopy(sender, e)
                    OnMenuDelete(sender, e)
                    trans.Commit()
                Finally
                    CType(trans, IDisposable).Dispose()
                End Try
            End If
        End Sub

        ''' 
        ''' Command handler for the Delete command.
        ''' 
        Private Sub OnMenuDelete(ByVal sender As Object, ByVal e As EventArgs)
            Dim host As IDesignerHost = CType(GetService(GetType(IDesignerHost)), IDesignerHost)
            If Not (host Is Nothing) AndAlso Not (m_currentSelection Is Nothing) AndAlso m_currentSelection.Count > 0 Then
                Dim trans As DesignerTransaction = host.CreateTransaction("Delete " + m_currentSelection.Count + " component(s)")
                Try
                    Dim o As Object
                    For Each o In m_currentSelection
                        If (TypeOf o Is IComponent) Then
                            ' In addition to destroying the component, we need to
                            ' remove it from our shape container.  If we were really
                            ' robust we would listen to the ComponentRemoved event
                            ' and automatically remove the component at that time,
                            ' but we are in total control of deleting components,
                            ' so we can simplify a little.
                            '
                            If (TypeOf o Is Shape) Then
                                ShapeContainer.Shapes.Remove(CType(o, Shape))
                                If ShapeContainer.Shapes.Count = 0 Then
                                    m_view.Invalidate()
                                End If
                            End If
                            host.DestroyComponent(CType(o, IComponent))
                        End If
                    Next o
                    trans.Commit()
                Finally
                    CType(trans, IDisposable).Dispose()
                End Try
            End If
        End Sub

        ''' 
        ''' Command handler for the Paste command.
        ''' 
        Private Sub OnMenuPaste(ByVal sender As Object, ByVal e As EventArgs)
            Dim host As IDesignerHost = CType(GetService(GetType(IDesignerHost)), IDesignerHost)
            If Not (host Is Nothing) Then
                Dim trans As DesignerTransaction = host.CreateTransaction("Paste")
                Try
                    ' Again we use the designer serialization service, but this time we
                    ' use it in reverse.  We have provided a data format name of
                    ' "ShapeLibraryData" so we can easily tell if the clipboard contains
                    ' something we can process.
                    '
                    Dim ds As IDesignerSerializationService = CType(GetService(GetType(IDesignerSerializationService)), IDesignerSerializationService)
                    If Not (ds Is Nothing) Then
                        Dim data As IDataObject = Clipboard.GetDataObject()
                        If Not (data Is Nothing) AndAlso data.GetDataPresent("ShapeLibraryData") Then
                            Dim objects As ICollection = ds.Deserialize(data.GetData("ShapeLibraryData"))
                            Dim o As Object
                            For Each o In objects
                                If TypeOf o Is IComponent Then
                                    host.Container.Add(CType(o, IComponent))
                                End If

                                ' Also add the shape to our container.  If this
                                ' is the first shape we add, we need to repaint
                                ' the entire UI because of the watermark text
                                ' we place on it.
                                If TypeOf o Is Shape Then
                                    ShapeContainer.Shapes.Add(CType(o, Shape))
                                    If ShapeContainer.Shapes.Count = 1 Then
                                        m_view.Invalidate()
                                    End If
                                End If
                            Next o

                            ' Finally, the general behavior of a paste is to select the components that
                            ' were just pasted.  We use a selection type of "Replace" to indicate that
                            ' we should never merge this selection with anything that is currently 
                            ' selected; we always replace it.
                            '
                            Dim selSvc As ISelectionService = CType(GetService(GetType(ISelectionService)), ISelectionService)
                            If Not (selSvc Is Nothing) Then
                                selSvc.SetSelectedComponents(objects, SelectionTypes.Replace)
                            End If
                        End If
                    End If
                    trans.Commit()
                Finally
                    CType(trans, IDisposable).Dispose()
                End Try
            End If
        End Sub

        ''' 
        ''' This is a speical callback command that is used to update the status of the
        ''' Paste command.  Normally changing the status of a command is done by us 
        ''' in response to some sort of change. For example, Cut, Copy and Delete are
        ''' all enabled whenever there is a non-zero selection and the base ShapeContainer
        ''' object isn't selected.  Paste is special, however, because enabling it is
        ''' based solely on the contents of the clipboard.  We have defined another
        ''' class below called ImmediateMenuCommand.  This class derives from
        ''' MenuCommand and has an additional event handler that is called whenever
        ''' Visual Studio wants to know the status of a command.  Here we check
        ''' the data type on the clipboard to see if it supports our format.
        ''' 
        Private Sub OnMenuPasteStatus(ByVal sender As Object, ByVal e As EventArgs)
            Dim cmd As MenuCommand = CType(sender, MenuCommand)
            Dim enabled As Boolean = False

            Dim data As IDataObject = Clipboard.GetDataObject()
            If Not (data Is Nothing) AndAlso data.GetDataPresent("ShapeLibraryData") Then
                enabled = True
            End If
            cmd.Enabled = enabled
        End Sub

        ''' 
        ''' Event handler for the selection changed event.  We draw the selection's
        ''' grab handles in our paint logic for the control, so when the selection changes
        ''' we need to tell the control to update.  We try to be optimial here and we
        ''' only update the areas of the control that have changed.
        ''' 
        Private Sub OnSelectionChanged(ByVal sender As Object, ByVal e As EventArgs)
            ' Run through the previous components and invalidate.
            '
            If Not (m_currentSelection Is Nothing) AndAlso Not (m_view Is Nothing) Then
                Dim o As Object
                For Each o In m_currentSelection
                    InvalidateShapeBounds(o)
                Next o
            End If

            m_currentSelection = CType(sender, ISelectionService).GetSelectedComponents()

            ' Now do the currently selected components.
            '
            If Not (m_currentSelection Is Nothing) AndAlso Not (m_view Is Nothing) Then
                Dim o As Object
                For Each o In m_currentSelection
                    InvalidateShapeBounds(o)
                Next o
            End If

            ' Now we must update our menu commands. Cut, copy and delete are all
            ' dependent on there being a selection to act on.
            '
            If Not (m_menuCommandService Is Nothing) Then
                Dim enableEditCommands As Boolean = False

                If Not (m_currentSelection Is Nothing) AndAlso m_currentSelection.Count > 0 Then
                    enableEditCommands = Not CType(sender, ISelectionService).GetComponentSelected(Component)
                End If

                Dim mc As MenuCommand = m_menuCommandService.FindCommand(StandardCommands.Cut)
                If Not (mc Is Nothing) Then
                    mc.Enabled = enableEditCommands
                End If

                mc = m_menuCommandService.FindCommand(StandardCommands.Copy)
                If Not (mc Is Nothing) Then
                    mc.Enabled = enableEditCommands
                End If

                mc = m_menuCommandService.FindCommand(StandardCommands.Delete)
                If Not (mc Is Nothing) Then
                    mc.Enabled = enableEditCommands
                End If
            End If
        End Sub

        ''' 
        ''' Our implementation of IRootDesigner.SupportedTechologies.  Our designer
        ''' uses Windows Forms as the UI technology, so we just return that enum value.
        ''' 
        Private ReadOnly Property SupportedTechnologies() As ViewTechnology() Implements IRootDesigner.SupportedTechnologies
            Get
                Return New ViewTechnology() {ViewTechnology.WindowsForms}
            End Get
        End Property

        ''' 
        ''' Our implementation of IRootDesigner.GetView.  This expects a windows
        ''' forms view technogy.  Our view is just a Windows Forms control that
        ''' has a paint handler that knows how to draw shapes.
        ''' 
        Private Function GetView(ByVal technology As ViewTechnology) As Object Implements IRootDesigner.GetView
            If technology <> ViewTechnology.WindowsForms Then
                Throw New ArgumentException("technology")
            End If

            ' Note that we store off a single instance of the
            ' view.  Don't always create a new object here, because
            ' it is possible that someone will call this multiple times.
            '
            If m_view Is Nothing Then
                m_view = New DesignerView(Me)
            End If
            Return m_view
        End Function

        ''' 
        ''' Our implementation of IToolboxUser.GetToolSupported.  This method is
        ''' actually never called.  In order for this method to be called, we
        ''' must have specified a ToolboxItemFilterType of Custom.  Our filtering
        ''' needs are very simple so we don't need to do that.  Custom filter types
        ''' should be used with caution because they can impact the performance of 
        ''' your designer.
        ''' 
        Private Function GetToolSupported(ByVal item As ToolboxItem) As Boolean Implements IToolboxUser.GetToolSupported
            Return True
        End Function

        ''' 
        ''' Our implementation of IToolboxUser.ToolPicked.  This is called by the
        ''' toolbox service when the user double clicks on a toolbox item.  The
        ''' item we get has already passed the filter test so we don't need to
        ''' do any additional checks.  Toolbox items know how to create their
        ''' own components, so creating a new shape here is really easy.
        ''' 
        Private Sub ToolPicked(ByVal item As ToolboxItem) Implements IToolboxUser.ToolPicked
            m_view.InvokeToolboxItem(item, m_view.ClientSize.Width / 2, m_view.ClientSize.Height / 2)
        End Sub

        ''' 
        ''' This class is the user interface we present for our designer.  All painting and
        ''' hit teseting logic is located within this class.
        ''' 
        Private Class DesignerView
            Inherits Control
            Private m_designer As ShapeContainerRootDesigner
            Private m_selectionSvc As ISelectionService
            Private m_host As IDesignerHost
            Private m_toolboxSvc As IToolboxService

            ' We perform hit testing during mouse move, and 
            ' these fields store the result of the hit test.
            ' They are updated each time the mouse moves.
            '
            Private m_hitObject As Object
            Private m_hitShape As Shape
            Private m_dragObject As DragObject
            Private m_toolboxItem As ToolboxItem

            Friend Sub New(ByVal designer As ShapeContainerRootDesigner)
                m_designer = designer

                ' Configure the control to support drag and drop.  We use
                ' this to support mouse drags on the control.
                AllowDrop = True
            End Sub

            ''' 
            ''' Private helper method to return a designer host.  We need a designer
            ''' host for several functions, and it's faster to stash it on the
            ''' control than get it through GetService over and over again.
            ''' 
            Private ReadOnly Property DesignerHost() As IDesignerHost
                Get
                    If m_host Is Nothing AndAlso Not (m_designer Is Nothing) Then
                        m_host = CType(m_designer.GetServiceVB(GetType(IDesignerHost)), IDesignerHost)
                    End If
                    Return m_host
                End Get
            End Property

            ''' 
            ''' Private helper method to return a selection service.  
            ''' It's faster to stash it on the
            ''' control than get it through GetService over and over again.
            ''' 
            Private ReadOnly Property SelectionService() As ISelectionService
                Get
                    If m_selectionSvc Is Nothing AndAlso Not (m_designer Is Nothing) Then
                        m_selectionSvc = CType(m_designer.GetServiceVB(GetType(ISelectionService)), ISelectionService)
                    End If
                    Return m_selectionSvc
                End Get
            End Property

            ''' 
            ''' Private helper method to return a toolbox service.  
            ''' It's faster to stash it on the
            ''' control than get it through GetService over and over again.
            ''' 
            Private ReadOnly Property ToolboxService() As IToolboxService
                Get
                    If m_toolboxSvc Is Nothing AndAlso Not (m_designer Is Nothing) Then
                        m_toolboxSvc = CType(m_designer.GetServiceVB(GetType(IToolboxService)), IToolboxService)
                    End If

                    Return m_toolboxSvc
                End Get
            End Property

            ''' 
            ''' This can be called to create the given toolbox item at the given location.
            ''' We hard-wire the default size of the shape.
            ''' 
            Friend Sub InvokeToolboxItem(ByVal item As ToolboxItem, ByVal x As Integer, ByVal y As Integer)
                ' User has clicked and we need to create a new item.
                '
                Dim host As IDesignerHost = DesignerHost

                ' For each new item created, move it to the location
                ' of the click.
                '
                If Not (host Is Nothing) Then
                    ' By creating a transaction here, we create a single undo unit that
                    ' encompasses everything we do until we commit.  This way if the user
                    ' clicks the undo button, the entire add / size work is undone in a
                    ' single action.
                    '
                    Dim trans As DesignerTransaction = host.CreateTransaction("Creating " + item.DisplayName)
                    Try
                        ' Nearly all toolbox items create a single component.  However,
                        ' some toolbox items (like data components), may invoke some sort
                        ' of wizard within the CreateComponents method and can return
                        ' multiple components.  It's best just to be generic here and
                        ' always handle multiple components.
                        '
                        Dim newComponents As IComponent() = item.CreateComponents(host)

                        Dim bounds As Rectangle = New Rectangle()
                        bounds.X = x - 50
                        bounds.Y = y - 50
                        bounds.Width = 100
                        bounds.Height = 100

                        Dim sc As ShapeContainer = Nothing
                        If TypeOf host.RootComponent Is ShapeContainer Then
                            sc = CType(host.RootComponent, ShapeContainer)
                        End If

                        Dim cs As IComponentChangeService
                        cs = CType(m_host.GetService(GetType(IComponentChangeService)), IComponentChangeService)
                        Dim shapesProperty As PropertyDescriptor
                        shapesProperty = TypeDescriptor.GetProperties(sc)("Shapes")
                        cs.OnComponentChanging(sc, shapesProperty)

                        Dim comp As IComponent
                        For Each comp In newComponents
                            If Not (sc Is Nothing) AndAlso TypeOf comp Is Shape Then
                                sc.Shapes.Add(CType(comp, Shape))
                                If sc.Shapes.Count = 1 Then
                                    Invalidate()
                                End If
                            End If

                            ' When setting properties at design time, always go through PropertyDescriptors.
                            ' If you don't, you will bypass some important notifications that Visual Studio
                            ' needs, and this can introduce problems with undo, code generation, and updates
                            ' to the property grid.
                            '
                            Dim boundsProp As PropertyDescriptor = TypeDescriptor.GetProperties(comp)("BoundingBox")
                            If Not (boundsProp Is Nothing) AndAlso boundsProp.PropertyType Is GetType(Rectangle) Then
                                boundsProp.SetValue(comp, bounds)
                                bounds.X += 10
                                bounds.Y += 10
                            End If
                        Next comp

                        ' Always commit a transaction when done.
                        '
                        trans.Commit()

                        ' This notifies the toolbox service that we did create this control.  The
                        ' toolbox service generally sets the currently selected tool to null in
                        ' this call.
                        Dim ts As IToolboxService = ToolboxService
                        If Not (ts Is Nothing) Then
                            ts.SelectedToolboxItemUsed()
                        End If

                        ' Finally, now that the user has created a new item, we should
                        ' selected them in the selection service.
                        '
                        Dim ss As ISelectionService = SelectionService
                        If Not (ss Is Nothing) Then
                            ss.SetSelectedComponents(newComponents, SelectionTypes.Replace)
                        End If
                    Finally
                        CType(trans, IDisposable).Dispose()
                    End Try
                End If
            End Sub

            ''' 
            ''' This overrides Control's OnDragDrop method to perform a drop 
            ''' operation.  All of the magic happens in our private DragObject
            ''' class.
            ''' 
            Protected Overrides Sub OnDragDrop(ByVal e As DragEventArgs)
                MyBase.OnDragDrop(e)

                If Not (m_dragObject Is Nothing) Then
                    ' Perform the actual drop.  We're being
                    ' defensive here -- if this throws an 
                    ' exception we don't want to keep
                    ' m_dragObject set, so we clear it first.
                    '
                    Dim d As DragObject = m_dragObject
                    m_dragObject = Nothing
                    d.Drop(e.X, e.Y)
                Else
                    ' Is this a toolbox item that is being dropped?
                    '
                    Dim ts As IToolboxService = ToolboxService
                    If Not (ts Is Nothing) AndAlso ts.IsToolboxItem(e.Data, DesignerHost) Then
                        Dim item As ToolboxItem = ts.DeserializeToolboxItem(e.Data, DesignerHost)
                        Dim pt As Point = New Point(e.X, e.Y)
                        pt = PointToClient(pt)
                        InvokeToolboxItem(item, pt.X, pt.Y)
                    End If
                End If
            End Sub

            ''' 
            ''' Called when a drag operation enters us.  Here we look to see
            ''' if the data within the drag event is our drag object.  If so,
            ''' we stash it off for later use.
            ''' 
            Protected Overrides Sub OnDragEnter(ByVal e As DragEventArgs)
                MyBase.OnDragEnter(e)
                Dim o As Object = e.Data.GetData(GetType(DragObject))
                If TypeOf o Is DragObject Then
                    m_dragObject = CType(o, DragObject)
                Else
                    m_dragObject = Nothing
                End If

                If Not (m_dragObject Is Nothing) Then
                    ' We only allow dragging from within the same designer.
                    ' Ensure this here.
                    '
                    If m_dragObject.DragControl Is Me Then
                        e.Effect = DragDropEffects.Move
                    Else
                        m_dragObject = Nothing
                        e.Effect = DragDropEffects.None
                    End If
                Else
                    ' Is this a toolbox item that is being dropped?
                    '
                    Dim ts As IToolboxService = ToolboxService
                    If Not (ts Is Nothing) AndAlso ts.IsToolboxItem(e.Data, DesignerHost) Then
                        e.Effect = DragDropEffects.Copy
                    End If
                End If
            End Sub

            ''' 
            ''' Clear the drag object we may have stored in OnDragEnter.
            ''' 
            Protected Overrides Sub OnDragLeave(ByVal e As EventArgs)
                MyBase.OnDragLeave(e)
                If Not (m_dragObject Is Nothing) Then
                    m_dragObject.ClearFeedback()
                    m_dragObject = Nothing
                End If
            End Sub

            ''' 
            ''' Overridden so we can give feedback about the drag.
            ''' 
            Protected Overrides Sub OnGiveFeedback(ByVal e As GiveFeedbackEventArgs)
                MyBase.OnGiveFeedback(e)

                If Not (m_dragObject Is Nothing) Then
                    m_dragObject.GiveFeedback()
                End If
            End Sub

            ''' 
            ''' We override OnMouseDown so that we can provide mouse UI
            ''' support to dragging shapes around.  On MouseDown we
            ''' locate the shape using hit testing so we can select it.
            ''' 
            Protected Overrides Sub OnMouseDown(ByVal m As MouseEventArgs)
                MyBase.OnMouseDown(m)

                ' establish some state so we know the mouse
                ' button is down
                '
                If m.Button = MouseButtons.Left Then
                    ' Select the object that we're currently over, or nothing
                    ' if we're not over anything.
                    '
                    Dim ss As ISelectionService = SelectionService
                    If Not (ss Is Nothing) Then
                        Dim hitObject As Object = Nothing

                        If m_hitShape Is Nothing Then
                            Dim host As IDesignerHost = DesignerHost
                            If Not (host Is Nothing) Then
                                hitObject = host.RootComponent
                            End If
                        Else
                            hitObject = m_hitShape
                        End If

                        If Not (hitObject Is Nothing) Then
                            ss.SetSelectedComponents(New Object() {hitObject}, SelectionTypes.Click)
                        End If
                    End If
                End If
            End Sub

            ''' 
            ''' This is called when the mouse moves.  Here we perform
            ''' hit-testing to see what shape the mouse is over.  If
            ''' the mouse button is down at the time, we also start a
            ''' drag operation.
            ''' 
            Protected Overrides Sub OnMouseMove(ByVal m As MouseEventArgs)
                ' Perform hit-testing of the shapes on the designer.
                '
                Dim newHitObject As Object = Nothing
                Dim newHitShape As Shape = Nothing

                Dim host As IDesignerHost = DesignerHost
                If Not (host Is Nothing) Then
                    Dim s As Shape
                    For Each s In m_designer.ShapeContainer.Shapes
                        Dim dobject As Object = host.GetDesigner(s)
                        Dim des As ShapeDesigner = Nothing
                        If TypeOf dobject Is ShapeDesigner Then
                            des = CType(dobject, ShapeDesigner)
                        End If
                        If Not (des Is Nothing) Then
                            newHitObject = des.GetHitTest(m.X, m.Y)
                            If Not (newHitObject Is Nothing) Then
                                newHitShape = s
                                Cursor = des.GetCursor(newHitObject)
                                Exit For
                            End If
                        End If
                    Next s
                End If

                m_hitObject = newHitObject
                m_hitShape = newHitShape

                ' If we are not hovering over a shape, stash off the
                ' current toolbox item.  Also give the toolbox a change
                ' to display its own cursor.
                '
                If newHitObject Is Nothing Then
                    Dim ts As IToolboxService = ToolboxService
                    If ts Is Nothing OrElse Not ts.SetCursor() Then
                        Cursor = Cursors.Default
                    Else
                        m_toolboxItem = ts.GetSelectedToolboxItem(DesignerHost)
                    End If
                End If

                ' Now, if a button is down, begin dragging.
                '
                If m.Button = MouseButtons.Left AndAlso Not (m_hitObject Is Nothing) Then
                    ' User stomped on a shape.  Move / size it.  Note that the
                    ' method DoDragDrop doesn't return until the drag/drop is actually
                    ' finished.
                    DoDragDrop(New DragObject(m_designer, Me, m_hitObject, m.X, m.Y), DragDropEffects.Move)
                End If
            End Sub

            ''' 
            ''' This is called when the mouse button is released.  We create a new toolbox
            ''' item here if we have one.  OnMouseMove actually sets
            ''' m_toolboxItem, but only if we're not hovering over any shapes.
            ''' 
            Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)
                MyBase.OnMouseUp(e)

                If e.Button = MouseButtons.Left AndAlso Not (m_toolboxItem Is Nothing) Then
                    InvokeToolboxItem(m_toolboxItem, e.X, e.Y)
                    m_toolboxItem = Nothing
                    Cursor = Cursors.Default
                End If
            End Sub

            ''' 
            ''' This overrides our control's paint event.  It performs
            ''' two services.  First, it draws all the shapes.  Next, it
            ''' draws all the adornments for the shapes.
            ''' 
            Protected Overrides Sub OnPaint(ByVal pe As PaintEventArgs)
                MyBase.OnPaint(pe)

                ' If we have shapes in our container, paint them.
                ' When we're done painting the shapes, run through
                ' all of the designers for shapes that are selected
                ' and ask them to draw their grab handles.
                If m_designer.ShapeContainer.Shapes.Count > 0 Then
                    pe.Graphics.SmoothingMode = SmoothingMode.AntiAlias
                    Dim s As Shape
                    For Each s In m_designer.ShapeContainer.Shapes
                        s.Draw(pe.Graphics)
                    Next s

                    Dim host As IDesignerHost = DesignerHost
                    Dim ss As ISelectionService = SelectionService
                    If Not (m_designer.m_currentSelection Is Nothing) AndAlso Not (host Is Nothing) AndAlso Not (ss Is Nothing) Then
                        Dim comp As Object
                        For Each comp In m_designer.m_currentSelection
                            If TypeOf comp Is IComponent Then
                                Dim desobject As Object = host.GetDesigner(CType(comp, IComponent))
                                Dim des As ShapeDesigner = Nothing
                                If TypeOf desobject Is ShapeDesigner Then
                                    des = CType(desobject, ShapeDesigner)
                                End If
                                If Not (des Is Nothing) Then
                                    Dim primary As Boolean
                                    If comp Is ss.PrimarySelection Then
                                        primary = True
                                    Else
                                        primary = False
                                    End If
                                    des.DrawAdornments(pe.Graphics, primary)
                                End If
                            End If
                        Next comp
                    End If
                Else
                    ' We have no shapes.  Draw a nice instructional message.
                    '
                    Dim format As StringFormat = New StringFormat()
                    format.Alignment = StringAlignment.Center
                    format.LineAlignment = StringAlignment.Center

                    Dim b As Rectangle = Bounds
                    Dim boundsF As RectangleF = New RectangleF(b.X, b.Y, b.Width, b.Height)
                    pe.Graphics.DrawString("Drag shape objects onto this designer surface from the toolbox.", Font, SystemBrushes.ControlDark, boundsF, format)
                End If
            End Sub

            ''' 
            ''' When dragging objects around on the designer, this class is used to hold
            ''' drag state information.
            ''' 
            Private Class DragObject
                Private m_designer As ShapeContainerRootDesigner
                Private m_dragControl As Control
                Private m_dragShapes As ArrayList
                Private m_hitObject As Object
                Private m_initialX As Integer
                Private m_initialY As Integer
                Private m_screenOffset As Point
                Private m_mouseOffset As Point
                Private m_removeOldDrag As Boolean

                Friend Sub New(ByVal designer As ShapeContainerRootDesigner, ByVal dragControl As Control, ByVal hitTestObject As Object, ByVal initialX As Integer, ByVal initialY As Integer)
                    m_designer = designer
                    m_dragControl = dragControl
                    m_hitObject = hitTestObject
                    m_initialX = initialX
                    m_initialY = initialY
                    m_mouseOffset = New Point(0, 0)
                    m_screenOffset = New Point(0, 0)
                    m_screenOffset = dragControl.PointToScreen(m_screenOffset)

                    ' The drag actually consists of all objects that are currently selected.
                    '
                    Dim ss As ISelectionService = CType(designer.GetServiceVB(GetType(ISelectionService)), ISelectionService)
                    Dim host As IDesignerHost = CType(designer.GetServiceVB(GetType(IDesignerHost)), IDesignerHost)

                    m_dragShapes = New ArrayList()

                    If Not (ss Is Nothing) AndAlso Not (host Is Nothing) Then
                        Dim selectedObjects As ICollection = ss.GetSelectedComponents()
                        Dim o As Object
                        For Each o In selectedObjects
                            If TypeOf o Is IComponent Then
                                Dim comp As IComponent = CType(o, IComponent)
                                Dim sdobject As Object = host.GetDesigner(comp)
                                If TypeOf sdobject Is ShapeDesigner Then
                                    Dim sd As ShapeDesigner = CType(sdobject, ShapeDesigner)
                                    m_dragShapes.Add(sd)
                                End If
                            End If
                        Next o
                    End If
                End Sub

                ''' 
                ''' This is the control that initiated the drag.
                ''' 
                Friend ReadOnly Property DragControl() As Control
                    Get
                        Return m_dragControl
                    End Get
                End Property

                ''' 
                ''' Called to clear any feedback drawing we have already done.
                ''' 
                Friend Sub ClearFeedback()
                    If m_removeOldDrag Then
                        m_removeOldDrag = False
                        Dim sd As ShapeDesigner
                        For Each sd In m_dragShapes
                            sd.DrawDragFeedback(m_hitObject, m_dragControl.BackColor, m_screenOffset, m_mouseOffset)
                        Next sd
                    End If
                End Sub

                ''' 
                ''' This performs the actual drop, which just moves all of our components
                ''' to the drop location.
                ''' 
                Friend Sub Drop(ByVal x As Integer, ByVal y As Integer)
                    ClearFeedback()
                    Dim pt As Point = New Point(x, y)
                    pt = m_dragControl.PointToClient(pt)
                    x = pt.X - m_initialX
                    y = pt.Y - m_initialY

                    ' If we have more than one shape, we need to wrap
                    ' in a designer transaction.
                    '
                    If m_dragShapes.Count > 1 Then
                        Dim host As IDesignerHost = CType(m_designer.GetServiceVB(GetType(IDesignerHost)), IDesignerHost)
                        If Not (host Is Nothing) Then
                            Dim trans As DesignerTransaction = host.CreateTransaction("Drag " + m_dragShapes.Count + " components")
                            Try
                                Dim sd As ShapeDesigner
                                For Each sd In m_dragShapes
                                    sd.Drag(m_hitObject, x, y)
                                Next sd

                                trans.Commit()
                            Finally
                                CType(trans, IDisposable).Dispose()
                            End Try
                        End If
                    Else
                        Dim sd As ShapeDesigner
                        For Each sd In m_dragShapes
                            sd.Drag(m_hitObject, x, y)
                        Next sd
                    End If
                End Sub

                ''' 
                ''' Called during dragging so we can update our feedback.
                ''' 
                Friend Sub GiveFeedback()
                    ' First, if we had prior feedback, clear it.
                    ClearFeedback()

                    ' Now, draw new feedback.  The feedback coordinates
                    ' are global and designed in such a way so that they
                    ' can be added to the shape coordinates directly.
                    ' To do this we take the global mouse position and
                    ' subtract off this position in client coordinates
                    ' of the drag control.
                    '
                    Dim currentPosition As Point = Control.MousePosition
                    m_mouseOffset.X = currentPosition.X - m_screenOffset.X - m_initialX
                    m_mouseOffset.Y = currentPosition.Y - m_screenOffset.Y - m_initialY

                    Dim sd As ShapeDesigner
                    For Each sd In m_dragShapes
                        sd.DrawDragFeedback(m_hitObject, m_dragControl.BackColor, m_screenOffset, m_mouseOffset)
                    Next sd

                    m_removeOldDrag = True
                End Sub
            End Class
        End Class

        ''' 
        ''' This is a special menu command suitable for menu items whose
        ''' availablility changes due to outside forces.  We use this
        ''' for the Paste menu command, which only becomes enabled when
        ''' there is something on the clipboard to paste.
        ''' 
        Private Class ImmediateMenuCommand
            Inherits MenuCommand
            Private m_statusHandler As EventHandler

            Friend Sub New(ByVal statusHandler As EventHandler, ByVal invokeHandler As EventHandler, ByVal id As CommandID)
                MyBase.new(invokeHandler, id)

                m_statusHandler = statusHandler
            End Sub

            ''' 
            ''' OleStatus returns a status code to the development environment
            ''' that indicates how it should treat this command.  We don't need
            ''' to know what the status codes are, we just need to invoke an event
            ''' that can update them.
            ''' 
            Public Overrides ReadOnly Property OleStatus() As Integer
                Get
                    If Not (m_statusHandler Is Nothing) Then
                        m_statusHandler.Invoke(Me, EventArgs.Empty)
                    End If
                    Return MyBase.OleStatus
                End Get
            End Property
        End Class
    End Class
End Namespace
